<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>Three.js Bones Browser</title>
		<link rel="shortcut icon" href="../../files/favicon.ico" />
		<link rel="stylesheet" type="text/css" href="../../files/main.css">
		<style>
			canvas {
				display: block;
				width: 100%;
				height: 100%;
			}

			#newWindow {
				display: block;
				position: absolute;
				bottom: 0.3em;
				left: 0.5em;
				color: #fff;
			}
		</style>
	</head>
	<body>
		<script type="importmap">
			{
				"imports": {
					"three": "../../build/three.module.js",
					"three/addons/": "../../examples/jsm/"
				}
			}
		</script>

		<a id='newWindow' href='./bones-browser.html' target='_blank'>Open in New Window</a>

		<script type="module">
			import {
				Bone,
				Color,
				CylinderGeometry,
				DirectionalLight,
				DoubleSide,
				Float32BufferAttribute,
				MeshPhongMaterial,
				PerspectiveCamera,
				Scene,
				SkinnedMesh,
				Skeleton,
				SkeletonHelper,
				Vector3,
				Uint16BufferAttribute,
				WebGLRenderer
			} from 'three';

			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

			let gui, scene, camera, renderer, orbit, lights, mesh, bones, skeletonHelper;

			const state = {
				animateBones: false
			};

			function initScene() {

				gui = new GUI();

				scene = new Scene();
				scene.background = new Color( 0x444444 );

				camera = new PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 200 );
				camera.position.z = 30;
				camera.position.y = 30;

				renderer = new WebGLRenderer( { antialias: true } );
				renderer.setPixelRatio( window.devicePixelRatio );
				renderer.setSize( window.innerWidth, window.innerHeight );
				document.body.appendChild( renderer.domElement );

				orbit = new OrbitControls( camera, renderer.domElement );
				orbit.enableZoom = false;

				lights = [];
				lights[ 0 ] = new DirectionalLight( 0xffffff, 3 );
				lights[ 1 ] = new DirectionalLight( 0xffffff, 3 );
				lights[ 2 ] = new DirectionalLight( 0xffffff, 3 );

				lights[ 0 ].position.set( 0, 200, 0 );
				lights[ 1 ].position.set( 100, 200, 100 );
				lights[ 2 ].position.set( - 100, - 200, - 100 );

				scene.add( lights[ 0 ] );
				scene.add( lights[ 1 ] );
				scene.add( lights[ 2 ] );

				window.addEventListener( 'resize', function () {

					camera.aspect = window.innerWidth / window.innerHeight;
					camera.updateProjectionMatrix();

					renderer.setSize( window.innerWidth, window.innerHeight );

				}, false );

				initBones();
				setupDatGui();

			}

			function createGeometry( sizing ) {

				const geometry = new CylinderGeometry(
					5, // radiusTop
					5, // radiusBottom
					sizing.height, // height
					8, // radiusSegments
					sizing.segmentCount * 3, // heightSegments
					true // openEnded
				);

				const position = geometry.attributes.position;

				const vertex = new Vector3();

				const skinIndices = [];
				const skinWeights = [];

				for ( let i = 0; i < position.count; i ++ ) {

					vertex.fromBufferAttribute( position, i );

					const y = ( vertex.y + sizing.halfHeight );

					const skinIndex = Math.floor( y / sizing.segmentHeight );
					const skinWeight = ( y % sizing.segmentHeight ) / sizing.segmentHeight;

					skinIndices.push( skinIndex, skinIndex + 1, 0, 0 );
					skinWeights.push( 1 - skinWeight, skinWeight, 0, 0 );

				}

				geometry.setAttribute( 'skinIndex', new Uint16BufferAttribute( skinIndices, 4 ) );
				geometry.setAttribute( 'skinWeight', new Float32BufferAttribute( skinWeights, 4 ) );

				return geometry;

			}

			function createBones( sizing ) {

				bones = [];

				let prevBone = new Bone();
				bones.push( prevBone );
				prevBone.position.y = - sizing.halfHeight;

				for ( let i = 0; i < sizing.segmentCount; i ++ ) {

					const bone = new Bone();
					bone.position.y = sizing.segmentHeight;
					bones.push( bone );
					prevBone.add( bone );
					prevBone = bone;

				}

				return bones;

			}

			function createMesh( geometry, bones ) {

				const material = new MeshPhongMaterial( {
					color: 0x156289,
					emissive: 0x072534,
					side: DoubleSide,
					flatShading: true
				} );

				const mesh = new SkinnedMesh( geometry,	material );
				const skeleton = new Skeleton( bones );

				mesh.add( bones[ 0 ] );

				mesh.bind( skeleton );

				skeletonHelper = new SkeletonHelper( mesh );
				skeletonHelper.material.linewidth = 2;
				scene.add( skeletonHelper );

				return mesh;

			}

			function setupDatGui() {

				let folder = gui.addFolder( 'General Options' );

				folder.add( state, 'animateBones' );
				folder.controllers[ 0 ].name( 'Animate Bones' );

				folder.add( mesh, 'pose' );
				folder.controllers[ 1 ].name( '.pose()' );

				const bones = mesh.skeleton.bones;

				for ( let i = 0; i < bones.length; i ++ ) {

					const bone = bones[ i ];

					folder = gui.addFolder( 'Bone ' + i );

					folder.add( bone.position, 'x', - 10 + bone.position.x, 10 + bone.position.x );
					folder.add( bone.position, 'y', - 10 + bone.position.y, 10 + bone.position.y );
					folder.add( bone.position, 'z', - 10 + bone.position.z, 10 + bone.position.z );

					folder.add( bone.rotation, 'x', - Math.PI * 0.5, Math.PI * 0.5 );
					folder.add( bone.rotation, 'y', - Math.PI * 0.5, Math.PI * 0.5 );
					folder.add( bone.rotation, 'z', - Math.PI * 0.5, Math.PI * 0.5 );

					folder.add( bone.scale, 'x', 0, 2 );
					folder.add( bone.scale, 'y', 0, 2 );
					folder.add( bone.scale, 'z', 0, 2 );

					folder.controllers[ 0 ].name( 'position.x' );
					folder.controllers[ 1 ].name( 'position.y' );
					folder.controllers[ 2 ].name( 'position.z' );

					folder.controllers[ 3 ].name( 'rotation.x' );
					folder.controllers[ 4 ].name( 'rotation.y' );
					folder.controllers[ 5 ].name( 'rotation.z' );

					folder.controllers[ 6 ].name( 'scale.x' );
					folder.controllers[ 7 ].name( 'scale.y' );
					folder.controllers[ 8 ].name( 'scale.z' );

				}

			}

			function initBones() {

				const segmentHeight = 8;
				const segmentCount = 4;
				const height = segmentHeight * segmentCount;
				const halfHeight = height * 0.5;

				const sizing = {
					segmentHeight: segmentHeight,
					segmentCount: segmentCount,
					height: height,
					halfHeight: halfHeight
				};

				const geometry = createGeometry( sizing );
				const bones = createBones( sizing );
				mesh = createMesh( geometry, bones );

				mesh.scale.multiplyScalar( 1 );
				scene.add( mesh );

			}

			function render() {

				requestAnimationFrame( render );

				const time = Date.now() * 0.001;

				//Wiggle the bones
				if ( state.animateBones ) {

					for ( let i = 0; i < mesh.skeleton.bones.length; i ++ ) {

						mesh.skeleton.bones[ i ].rotation.z = Math.sin( time ) * 2 / mesh.skeleton.bones.length;

					}

				}

				renderer.render( scene, camera );

			}

			initScene();
			render();

		</script>
	</body>
</html>
